package cn.felord.app.security.filter;

import cn.felord.app.security.*;
import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Objects;

/**
 * jwt 鉴别
 *
 * @author Dax
 * @since 17:49  2018/9/20
 */
@Slf4j
public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
    private static final String AUTHENTICATION_PREFIX = "Bearer ";


    private JwtTokenGenerator jwtTokenGenerator;

    private JwtTokenStorage jwtTokenStorage;


    public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtTokenGenerator jwtTokenGenerator, JwtTokenStorage jwtTokenStorage) {
        super(authenticationManager);
        this.jwtTokenGenerator = jwtTokenGenerator;
        this.jwtTokenStorage = jwtTokenStorage;
    }

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager, authenticationEntryPoint);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 如果已经通过认证
        if (SecurityContextHolder.getContext().getAuthentication() != null) {
            chain.doFilter(request, response);
            return;
        }

        String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        if (StringUtils.hasText(header) && header.startsWith(AUTHENTICATION_PREFIX)) {
            String jwtToken = header.replace(AUTHENTICATION_PREFIX, "");
            authenticationTokenHandle(jwtToken, request);
        }
        chain.doFilter(request, response);

    }

    private void authenticationTokenHandle(String jwtToken, HttpServletRequest request) {

        if (StringUtils.hasText(jwtToken)) {
            JwtClaims jwtClaims = jwtTokenGenerator.decodeAndVerify(jwtToken);
            if (Objects.nonNull(jwtClaims)) {
                String userId = jwtClaims.getAud();
                String expireTime = jwtClaims.getExp();
                // 是否过期
                if (isExpired(expireTime)) {
                    log.info("token : {}  is  expired", jwtToken);
                    return;
                }
                // 从缓存获取 token
                JwtPair jwtPair = jwtTokenStorage.get(userId);
                if (Objects.isNull(jwtPair)) {
                    log.info("token : {}  is  not in cache", jwtToken);
                    return;
                }
                String accessToken = jwtPair.getAccessToken();

                if (jwtToken.equals(accessToken)) {
                    String uname = jwtClaims.getUname();
                    List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(ArrayUtil.toArray(jwtClaims.getRoles(), String.class));
                    SecureUser user = new SecureUser(userId, uname, "[PROTECTED]", authorities);
                    UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(user, null, authorities);
                    usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                }
            }

        }

    }

    /**
     * 判断是否过期
     *
     * @param exp jwt exp
     * @return is exp or not
     */
    private boolean isExpired(String exp) {
        return LocalDateTime.now().isAfter(LocalDateTime.parse(exp, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
    }


}
